/*
 * Copyright (c) 2013-2018 上海汇数数据. All rights reserved.
 * @(#) HexKt.java 2018-08-05 22:09
 */

package cn.jh.common.core.base;

import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;

import java.nio.charset.Charset;

import static com.google.common.base.Preconditions.checkArgument;
import static java.nio.charset.StandardCharsets.UTF_8;


/**
 * 16进制 与 2进制 数据转换工具。
 *
 * @author Fuchun
 * @since 1.0
 */
public class HexKt {

    private static final byte[] EMPTY = new byte[0];
    private static final char[] hexDigits = "0123456789abcdef".toCharArray();

    /**
     * 将给定的数据转换成16进制字符串。
     * <pre>{@code
     * HexKt.encode((byte[]) null) = null
     * HexKt.encode(new byte[0])   = ""
     * }</pre>
     *
     * @param bytes 数据。
     * @return 返回给定的数据的16进制字符串。
     */
    @Contract("null -> null")
    public static String encode(byte[] bytes) {
        if (bytes == null) return null;
        if (bytes.length == 0) return "";
        StringBuilder sb = new StringBuilder(2 * bytes.length);
        for (byte b : bytes) {
            sb.append(hexDigits[(b >> 4) & 0xf]).append(hexDigits[b & 0xf]);
        }
        return sb.toString();
    }

    @Contract("null -> null")
    public static String encode(String src) {
        return encode(src, UTF_8);
    }

    public static String encode(String src, Charset charset) {
        return encode(src.getBytes(charset == null ? UTF_8 : charset));
    }

    /**
     * 将16进制字符串解码为2进制数据。
     * <pre>{@code
     * HexKt.decode(null) = byte[0]
     * HexKt.decode("")   = byte[0]
     *
     * }</pre>
     *
     * @param hexString 16进制字符串。
     * @return 返回给定字符串表示的二进制数据。
     * @throws IllegalArgumentException 如果给定的字符串不能表示为16进制数据。
     */
    @NotNull
    public static byte[] decode(String hexString) {
        if (hexString == null || hexString.isEmpty()) return EMPTY;
        checkArgument(hexString.length() >= 2,
                "input string (%s) must have at least 2 characters", hexString);
        checkArgument(hexString.length() % 2 == 0,
                "input string (%s) must have an even number of characters", hexString);
        byte[] bytes = new byte[hexString.length() / 2];
        for (int i = 0; i < hexString.length(); i += 2) {
            int ch1 = decode(hexString.charAt(i)) << 4;
            int ch2 = decode(hexString.charAt(i + 1));
            bytes[i / 2] = (byte) (ch1 + ch2);
        }
        return bytes;
    }

    public static String decodeToString(String hexString) {
        return decodeToString(hexString, UTF_8);
    }

    public static String decodeToString(String hexString, Charset charset) {
        byte[] bytes = decode(hexString);
        if (bytes.length == 0) {
            return null;
        }
        return new String(bytes, charset == null ? UTF_8 : charset);
    }

    private static int decode(char ch) {
        if (ch >= '0' && ch <= '9') {
            return ch - '0';
        }
        if (ch >= 'a' && ch <= 'f') {
            return ch - 'a' + 10;
        }
        throw new IllegalArgumentException("Illegal hexadecimal character: " + ch);
    }

    private HexKt() {}
}
